/* Copyright (C) 2009 Mobile Sorcery AB

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License, version 2, as published by
the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License
along with this program; see the file COPYING.  If not, write to the Free
Software Foundation, 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
*/

#include <MAUtil/Moblet.h>
#include <MAUtil/Connection.h>
#include <MAUtil/util.h>
#include <MAUtil/Map.h>
#include <MAUtil/String.h>
#include <MAUtil/Geometry.h>
#include <MAUI/Font.h>
#include <conprint.h>

#include <MAInterpreter/InterpreterMoblet.h>
#include <MAInterpreter/LuaInterpreter.h>
#include <MAInterpreter/InterpreterErrorListener.h>
#include <MAInterpreter/System.h>

#include "MAHeaders.h"

using namespace MAUtil;

// Server address and port.
#define SERVER_DEFAULT_ADDRESS "192.168.0.187"
//#define SERVER_DEFAULT_ADDRESS "modev.mine.nu"
#define SERVER_PORT ":55555"
#define SERVER_URL_LOCALHOST "socket://localhost:55555"

//
// Protocol specification
//
// The first 4 bytes of a message is a command integer.
// The next 4 bytes is an integer with the size of the rest
// of the message.
//
// After these binary integer values follows the message content,
// if any. There is always a size integer, event if there is
// no content. This is done to simplify the protocol implementation.
// If theer is no data, the size should be zero.
//
// Thus we have:
//   command - 4 byte integer
//   data size - 4 byte integer, is 0 if there is no data
//   optional data
//

// Command constants.

// Run Lua code on the client. After this command follows
// a length int and a string of byte-size characters.
#define COMMAND_RUN_LUA_SCRIPT 1

// Reset the interpreter state.
#define COMMAND_RESET 2

// Reply from client to server. After this command follows
// a length int and a string of byte-size characters.
#define COMMAND_REPLY 3

// The purpose of this script if to make it possible to
// exit the application on startup, or exit if the connection
// to the LiveEditor/Server fails.
// The keyDown function can of course be changed by the user.
static char* InitialLuaScript =
	"System.onKeyDown(function(key) "
	"  if MAK_BACK == key or MAK_0 == key then "
	"    maExit(0) "
	"  end "
	"end)"
	;

// While this is true we should run the application.
static bool gRun = true;

/**
 * Helper function that displays a text message on the screen.
 */
static void DisplayText(const char* text)
{
	MAUI::Font font(ARIAL20BOLDWHITE);
	MAUtil::Rect rect;

	rect.y = 0;
	rect.y = 0;
	rect.width = EXTENT_X(maGetScrSize());
	rect.height = EXTENT_Y(maGetScrSize());

	maSetColor(0x000000);
	maFillRect(rect.x, rect.y, rect.width, rect.height);

	rect.x += 10;
	rect.y += 10;
	rect.width -= 20;
	rect.height -= 20;
	font.setLineSpacing(5);
	font.drawBoundedString(text, 10, 10, rect);

	maUpdateScreen();
}

/**
 * Helper function that writes an int to a byte buffer.
 */
static void WriteIntToBuffer(Vector<byte> &buffer, int index, int value)
{
	buffer[index] = (value) & 0xFF;
	buffer[index + 1] = (value >> 8) & 0xFF;
	buffer[index + 2] = (value >> 16) & 0xFF;
	buffer[index + 3] = (value >> 24) & 0xFF;
}

/**
 * Program that runs a client that listens for Lua code.
 *
 * Server port is changed to 55555.
 */
class LiveMoblet :
	public MAInterpreter::InterpreterMoblet,
	public MAInterpreter::InterpreterErrorListener,
	public ConnectionListener,
	public TextBoxListener
{
public:
	LiveMoblet(MAInterpreter::AbstractInterpreter* interpreter) :
		InterpreterMoblet(interpreter),
		mConnection(this),
		mServerUrl(SERVER_URL_LOCALHOST)
	{
		addTextBoxListener(this);

		// Read ipaddress from store.
		char* ipAddress = MAInterpreter::System::readStringFromStore(
			"LastIpAddress");
		if (NULL == ipAddress)
		{
			// Store does not exist, allocate buffer for the ipaddress
			// and use the default address (no error checking).
			ipAddress = (char*) malloc(strlen(SERVER_DEFAULT_ADDRESS) + 1);
			strcpy(ipAddress, SERVER_DEFAULT_ADDRESS);
		}

		wchar* wIpAddress = MAInterpreter::System::charToWideCharString(ipAddress);
		int result = maTextBox(L"Server ip:", wIpAddress, mServerIp, 256, 0);
		if (IOCTL_UNAVAILABLE == result)
		{
			// If we don't have maTextBox we init the client to connect
			// to a server running at localhost.
			initClient();
		}

		// These are not needed any more.
		free(ipAddress);
		free(wIpAddress);
    }

	void initClient()
	{
		DisplayText("Connecting to LiveEditor, please wait.");
        int result = mConnection.connect(mServerUrl.c_str());
        if (result < 0)
        {
            maPanic(result, "mConnection.connect failed");
        }
        interpreter->setInterpreterErrorListener(this);
	}

    virtual ~LiveMoblet()
    {
    }

    void onInterpreterError(const char* errorMessage)
    {
    	mLastLuaError = errorMessage;
    }

    void connectFinished(Connection* conn, int result)
    {
    	if (result < 0)
    	{
    		DisplayText("Failed to connect to LiveEditor.");
    		printf("mConnection.connectFinished failed");
    		return;
    	}

    	DisplayText("Successfully connected to LiveEditor.");

		// Start download of messages from the server.
		downloadMessage();
	}

	void connWriteFinished(Connection* conn, int result)
	{
		if (result < 0)
		{
			printf("mConnection.write failed");
			return;
		}

		// Download next message.
		downloadMessage();
	}

	void connRecvFinished(Connection* conn, int result)
	{
		// Check for error.
		if (result < 0)
		{
			printf("mConnection.recv failed: %i", result);
			return;
		}

		// Tell the buffer about the new data size.
		mInBuffer.resize(mInBuffer.size() + result);

		// Have we downloaded the whole message?
		bool isMessageDownloaded = downloadMessage();
		if (isMessageDownloaded)
		{
			// The command is in the first 32 bit integer.
			int command = ((int*)&mInBuffer[0])[0];
			switch (command)
			{
				case COMMAND_RUN_LUA_SCRIPT:
					// Zero terminate buffer.
					mInBuffer.resize(mInBuffer.size() + 1);
					mInBuffer[mInBuffer.size() - 1] = 0;

					// Point to the start of the script data.
					const char* script = (const char*) &mInBuffer[8];

					// Evaluate.
					int success = interpreter->eval(script);

					// Clear the input buffer, this makes room for
					// the next message.
					mInBuffer.clear();

					// Write response.
					if (success)
					{
						mResponseMessage = "Success";
					}
					else
					{
						mResponseMessage = "Error: " + mLastLuaError;
					}
					lprintfln("Response: %s", mResponseMessage.c_str());
					writeResponse(mResponseMessage.c_str());

					break;

				case COMMAND_RESET:
					// Clear the input buffer.
					mInBuffer.clear();

					// Write response.
					writeResponse("Client is reset");

					// Exit Moblet event loop.
					// TODO: Uncomment.
					// exitRun();

					break;
			}
		}
	}

    /**
     * Returns true if a complete message is downloaded,
     * otherwise returns false.
     */
	bool downloadMessage()
	{
		// First 8 bytes contain command id and message
		// data size, check that we have got those bytes.
		if (mInBuffer.size() >= 8)
		{
			// Data size is in the second 32 bit int.
			int dataSize = ((int*)&mInBuffer[0])[1];
			int messageSize = 8 + dataSize;

			lprintfln("Data size size is: %i", dataSize);

			// Have we got all the data?
			if (mInBuffer.size() == messageSize)
			{
				lprintfln("Got all data");

				// We have all data.
				return true;
			}
		}

		// Read more data, read in 1024 byte chunks.
		int offset = mInBuffer.size();
		mInBuffer.reserve(offset + 1024);
		mConnection.recv(&mInBuffer[offset], 1024);

		// We have not yet got all data.
		return false;
	}

    void writeResponse(const char* data)
    {
    	// Resize the output buffer.

    	// Message size is 8 bytes plus string length.
    	int dataSize = strlen(data);
    	int messageSize = 8 + dataSize;
    	mOutBuffer.reserve(messageSize);
    	mOutBuffer.resize(messageSize);

    	// Write integer with command id.
    	WriteIntToBuffer(mOutBuffer, 0, COMMAND_REPLY);

    	// Write integer with data size.
    	WriteIntToBuffer(mOutBuffer, 4, dataSize);

    	//printf("Response: %s %i %i", data, length, (int)(*(int*)&outBuffer[0]));

    	// Copy string data to buffer.
    	memcpy(&mOutBuffer[8], data, dataSize);

    	// Write buffer to network.
     	mConnection.write(&mOutBuffer[0], messageSize);
    }

    void textBoxClosed(int result, int textLength)
    {
    	if (result == MA_TB_RES_CANCEL)
    	{
    		maExit(1);
    	}

    	// Convert ipaddress from wchar to char (no error checking).
		char* ipAddress = MAInterpreter::System::wideCharToCharString(mServerIp);

    	// Save ipaddress to store (just hope it works, no error checking).
		MAInterpreter::System::writeStringToStore(
			"LastIpAddress", ipAddress);

    	mServerUrl = "socket://";
// Old code for converting wchar to char.
//    	for (int i = 0; i < textLength; i++)
//    	{
//    		mServerUrl += (char)mServerIp[i];
//    	}
    	mServerUrl += ipAddress;
    	mServerUrl += SERVER_PORT;

    	printf("Server url: %s\n", mServerUrl.c_str());

    	initClient();
    }

private:
 	Vector<byte> mInBuffer;
	Vector<byte> mOutBuffer;
 	Connection mConnection;
 	String mLastLuaError;
 	String mResponseMessage;
 	String mServerUrl;
 	wchar_t mServerIp[256];
};

extern "C" int MAMain()
{
	gConsoleDisplay = 0;

	// Variables that point to the singe instance of
	// the interpreter and the moblet.
	LiveMoblet* moblet = NULL;

	while (gRun)
	{
		// If we have a previous moblet instance, delete it.
		// This will also delete the interpreter instance.
		if (moblet) { delete moblet; }

		// Create Lua interpreter and Moblet.
		MAInterpreter::LuaInterpreter* interpreter = new MAInterpreter::LuaInterpreter();
		if (!interpreter->initialize())
		{
			maPanic(1, "Could not initialize Lua!");
		}
		moblet = new LiveMoblet(interpreter);
		if (!moblet)
		{
			maPanic(1, "Could not create Moblet!");
		}

		// Bind fonts.
		MAInterpreter::Sys()->addFont("Arial20BoldBlack", ARIAL20BOLDBLACK);
		MAInterpreter::Sys()->addFont("Arial32BoldBlack", ARIAL32BOLDBLACK);
		MAInterpreter::Sys()->addFont("Arial20BoldWhite", ARIAL20BOLDWHITE);
		MAInterpreter::Sys()->addFont("Arial32BoldWhite", ARIAL32BOLDWHITE);

		// Evaluate the initialization script before we enter run.
		interpreter->eval(InitialLuaScript);

		// Enter the event loop.
		// TODO: Chenge to: moblet->run();
		Moblet::run(moblet);

		// Empty the MoSync event queue.
		MAEvent event;
		while(maGetEvent(&event));
	}

    return 0;
};
